package com.xuzz.study.spring.simplemvc.core;

import com.xuzz.study.spring.simplemvc.annotation.XzzAutowired;
import com.xuzz.study.spring.simplemvc.annotation.XzzController;
import com.xuzz.study.spring.simplemvc.annotation.XzzRequestMapping;
import com.xuzz.study.spring.simplemvc.annotation.XzzService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class XzzDispatchServlet extends HttpServlet {

    //对应web.xml中的param-name
    private static final String LOCATION="contextConfigLocation";

    //保存配置信息
    private Properties properties = new Properties();

    //保存所有被扫描的相关类名
    private List<String> classNames = new ArrayList<>();

    //保存所有初始化的bean
    private Map<String,Object> ioc = new HashMap<>();

    //保存所有的url和方法
    private Map<String, Method> handlerMapping = new HashMap<>();

    public XzzDispatchServlet()
    {
        super();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        //1、加载配置文件
        doLoadConfig(config.getInitParameter(LOCATION));

        //2、扫描类,保存到classNames中
        doScan(properties.get("scanPackage").toString());

        //3、根据classNames，初始化相关类并保存到ioc容器中
        doInstance();

        //4、依赖注入（给有autowired的filed赋值)
        doAutowired();

        //5、构造handlermapping
        initHandlerMapping();

        //6、等待请求

        System.out.println("xzz mvc framework is init");

    }

    /**
     * 加载配置，并读到Properties对象中
     * @param location
     */
    private void doLoadConfig(String location)
    {
        InputStream fis = null;
        try
        {
            fis = this.getClass().getClassLoader().getResourceAsStream(location);
            properties.load(fis);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally {
            try
            {
                if(null!=fis)
                {
                    fis.close();
                }
            }
            catch (IOException ex)
            {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 扫描所有class文件
     * @param packageName
     */
    private void doScan(String packageName)
    {
        URL url = this.getClass().getClassLoader()
                .getResource("/"+packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());
        for(File file:dir.listFiles())
        {
            if(file.isDirectory())
            {
                doScan(packageName+"."+file.getName());
            }
            else
            {
                classNames.add(
                        packageName+"."+file.getName()
                                .replace(".class","")
                                .trim()
                );
            }
        }
    }

    private String lowerFirstCase(String str)
    {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doInstance()
    {
        if(classNames.size()<=0)
        {
            return;
        }
        try
        {
            for(String className:classNames)
            {
                /**
                 * java中class.forName()和classLoader都可用来对类进行加载。
                 * class.forName()前者除了将类的.class文件加载到jvm中之外，还会对类进行解释，执行类中的static块。
                 * 而classLoader只干一件事情，就是将.class文件加载到jvm中，不会执行static中的内容,只有在newInstance才会去执行static块。
                 * Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数，创建类的对象                 *
                 */
                Class<?> clazz = Class.forName(className);
                if(clazz.isAnnotationPresent(XzzController.class))
                {
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName,clazz.newInstance());
                }
                else if(clazz.isAnnotationPresent(XzzService.class))
                {
                    XzzService service = clazz.getAnnotation(XzzService.class);
                    String beanName = service.value();
                    if(!"".equals(beanName.trim()))
                    {
                        ioc.put(beanName,clazz.newInstance());
                        continue;
                    }

                    Class<?>[] interfaces = clazz.getInterfaces();
                    for(Class<?> i:interfaces)
                    {
                        ioc.put(i.getName(),clazz.newInstance());
                    }
                }
                else
                {
                    continue;
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private void doAutowired()
    {
        if(ioc.isEmpty())
        {
            return;
        }
        for(Map.Entry<String,Object> entry:ioc.entrySet())
        {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for(Field field:fields)
            {
                if(!field.isAnnotationPresent(XzzAutowired.class))
                {
                    continue;
                }
                XzzAutowired autowired = field.getAnnotation(XzzAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName))
                {
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);
                try {
                    //给field 赋值
                    field.set(entry.getValue(),ioc.get(beanName));
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    private void initHandlerMapping()
    {
        if(ioc.isEmpty())
        {
            return;
        }

        //ioc存放的是相关类的实例
        for(Map.Entry<String,Object> entry:ioc.entrySet())
        {
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(XzzController.class))
            {
                continue;
            }

            String baseUrl = "";
            if(clazz.isAnnotationPresent(XzzRequestMapping.class))
            {
                XzzRequestMapping requestMapping = clazz.getAnnotation(XzzRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //获取method的url配置
            Method[] methods = clazz.getMethods();
            for(Method method:methods)
            {
                if(!method.isAnnotationPresent(XzzRequestMapping.class))
                {
                    continue;
                }

                XzzRequestMapping methodRequestMapping = method.getAnnotation(XzzRequestMapping.class);
                String url = ("/"+baseUrl+"/"+methodRequestMapping.value().replaceAll("/+","/"));
                handlerMapping.put(url,method);
                System.out.println("mapped "+url+"=>"+method);
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try
        {
            doDispatch(req,resp);
        }
        catch (Exception e)
        {
            e.printStackTrace();
            resp.getWriter().write("500"
            +Arrays.toString(e.getStackTrace())
            .replaceAll("\\[|\\]","")
            .replaceAll(",\\s","\r\n"));
        }
    }

    private void doDispatch(HttpServletRequest req,HttpServletResponse resp)
        throws Exception
    {
        if(this.handlerMapping.isEmpty())
        {
            return;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();

        url = url.replace(
                contextPath,""
        ).replaceAll("/+","/");

        if(!this.handlerMapping.containsKey(url))
        {
            resp.getWriter().write("404 not found");
            return;
        }

        Method method = this.handlerMapping.get(url);

        Class<?>[] parameterTypes = method.getParameterTypes();
        Map<String,String[]> parameterMap = req.getParameterMap();

        Object[] paramValues = new Object[parameterTypes.length];

        for(int i=0;i<parameterTypes.length;i++)
        {
            Class parameterType = parameterTypes[i];
            if(parameterType==HttpServletRequest.class)
            {
                paramValues[i]=req;
                continue;
            }
            else if(parameterType==HttpServletResponse.class)
            {
                paramValues[i]=resp;
                continue;
            }
            else if(parameterType==String.class)
            {
                for(Map.Entry<String,String[]> param:parameterMap.entrySet())
                {
                    String value = Arrays.toString(param.getValue())
                            .replaceAll("\\[|\\]","")
                            .replaceAll(",\\s","");
                    paramValues[i]=value;
                }
            }
        }

        try
        {
            String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());
            method.invoke(this.ioc.get(beanName),paramValues);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
